package io.grpc.benchmarks.qps;

import io.grpc.Server;
import io.grpc.benchmarks.qps.service.TestServiceImpl;
import io.grpc.netty.GrpcSslContexts;
import io.grpc.netty.NettyServerBuilder;
import io.grpc.testing.TestServiceGrpc;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.ServerChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.SslProvider;

import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;

/**
 * --address=127.0.0.1:8888
 * Created by xinxingegeya on 15/12/15.
 */
public class AsyncServer {

    /**
     * checkstyle complains if there is no javadoc comment here.
     */
    public static void main(String... args) throws Exception {
        new AsyncServer().run(args);
    }

    /**
     * Equivalent of "main", but non-static.
     */
    public void run(String[] args) throws Exception {
        ServerConfiguration.Builder configBuilder = ServerConfiguration
            .newBuilder();
        ServerConfiguration config;
        try {
            config = configBuilder.build(args);
        } catch (Exception e) {
            System.out.println(e.getMessage());
            configBuilder.printUsage();
            return;
        }

        final Server server = newServer(config);
        server.start();

        System.out.println("QPS Server started on " + config.address);

        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                try {
                    System.out.println("QPS Server shutting down");
                    server.shutdown();
                    server.awaitTermination(5, TimeUnit.SECONDS);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    static Server newServer(ServerConfiguration config) throws IOException {
        SslContext sslContext = null;
        if (config.tls) {
            System.out.println("Using fake CA for TLS certificate.\n"
                + "Run the Java client with --tls --testca");

            File cert = TestUtils.loadCert("server1.pem");
            File key = TestUtils.loadCert("server1.key");
            SslContextBuilder sslContextBuilder = GrpcSslContexts
                .forServer(cert, key);
            if (config.transport == ServerConfiguration.Transport.NETTY_NIO) {
                sslContextBuilder = GrpcSslContexts.configure(sslContextBuilder,
                    SslProvider.JDK);
            } else {
                // Native transport with OpenSSL
                sslContextBuilder = GrpcSslContexts.configure(sslContextBuilder,
                    SslProvider.OPENSSL);
            }
            if (config.useDefaultCiphers) {
                sslContextBuilder.ciphers(null);
            }
            sslContext = sslContextBuilder.build();
        }

        final EventLoopGroup boss;
        final EventLoopGroup worker;
        final Class<? extends ServerChannel> channelType;
        switch (config.transport) {
            case NETTY_NIO: {
                boss = new NioEventLoopGroup();
                worker = new NioEventLoopGroup();
                channelType = NioServerSocketChannel.class;
                break;
            }
            case NETTY_EPOLL:
            case NETTY_UNIX_DOMAIN_SOCKET:
            default: {
                // Should never get here.
                throw new IllegalArgumentException(
                    "Unsupported transport: " + config.transport);
            }
        }

        NettyServerBuilder builder = NettyServerBuilder
            .forAddress(config.address).bossEventLoopGroup(boss)
            .workerEventLoopGroup(worker).channelType(channelType)
            .addService(TestServiceGrpc.bindService(new TestServiceImpl()))
            .sslContext(sslContext).flowControlWindow(config.flowControlWindow);
//        if (config.directExecutor) {
//            builder.directExecutor();
//        }
        return builder.build();
    }

}
